#
import re
import bisect

def parse(filename):
    """ Parses gc.log file and returns list of 100-nanosec time intervals (running, stopped, running and so on).

    The even-positioned intervals are intervals where mutator threads are running.
    The odd-positioned intervals are intervals where mutator threads are stopped.
    The result list is always ends with 'running' interval, i.e. its size is always odd.
    """
    stopped_pattern = re.compile('.*Total time for which application threads were stopped: (\\d*\\.\\d*) seconds')
    running_pattern = re.compile('.*Application time: (\\d*\\.\\d*) seconds')
    patterns = [ running_pattern, stopped_pattern ]
    result = []
    with open(filename) as file:
        for line in file:
            idx = len(result) & 1
            matcher = patterns[idx].match(line)
            if matcher:
                result.append(__extract(matcher))
            else:
                matcher = patterns[1 - idx].match(line)
                if matcher:
                    result.append(0)
                    result.append(__extract(matcher))
        if (len(result) & 1) == 0:
            result.append(0)
    return result

def __extract(matcher):
    return int(float(matcher.group(1)) * 1e7)

class TimeIndexException(Exception):
    def __init__(self, moment, length):
        self.moment = moment
        self.length = length
    def __str__(self):
        return '0 <= ' + repr(self.moment) + ' <= ' + repr(self.length)

class RunningStoppedTimes:
    def __init__(self, times):
        self.accum_general = []
        self.accum_mutator = []
        accum = 0
        for i in range(0, len(times), 1):
            accum += times[i]
            self.accum_general.append(accum)
        if len(self.accum_general) == 0:
            self.accum_general.append(0)
        accum = 0
        for i in range(0, len(times), 2):
            accum += times[i]
            self.accum_mutator.append(accum)
        if len(self.accum_mutator) == 0:
            self.accum_mutator.append(0)

    def __range_check(self, moment):
        total_time = self.total_time()
        if not (0 <= moment <= total_time):
            raise TimeIndexException(moment, total_time)
        
    def total_time(self):
        """ Returns total time length in 100ns units """
        n = len(self.accum_general)
        return self.accum_general[n - 1] if n > 0 else 0

    def running_time(self):
        """ Returns total 'running' time in 100ns units """
        n = len(self.accum_mutator)
        return self.accum_mutator[n - 1] if n > 0 else 0

    def stopped_time(self):
        """ Returns total 'stopped' time in 100ns units """
        return self.total_time() - self.running_time()

    def mutator_time(self, moment, window):
        """ Returns Mutator Time at specified moment with specified time window """
        return self.accumulated_mutator_time(moment + window) - self.accumulated_mutator_time(moment)

    def accumulated_mutator_time(self, moment):
        """ Returns Mutator Time from 0 till specified moment """
        self.__range_check(moment)
        i = bisect.bisect_left(self.accum_general, moment)
        result = self.accum_mutator[i >> 1]
        if ((i & 1) == 0):
            result += moment - self.accum_general[i]
        return result

    def minimum_mutator_time(self, window):
        """ Returns Minimum Mutator Time with specified time window for whole period """
        total_time = self.total_time()
        result = self.accumulated_mutator_time(window)
        for i in range(len(self.accum_general)):
            acc_gen = self.accum_general[i]
            acc_mut = self.accum_mutator[i >> 1]
            if acc_gen + window > total_time: break
            acc_mutr = self.accumulated_mutator_time(acc_gen + window)
            time = acc_mutr - acc_mut
            if time < result: result = time
        return result
